using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using CavemanTools.Reflection;

namespace CavemanTools.Extensions
{
	public static class ObjectExtend
	{
		
		
		/// <summary>
		/// Creates dictionary from object properties.
		/// </summary>
		/// <param name="value">Object</param>
		/// <returns></returns>
		public static IDictionary<string,object> ToDictionary(this object value)
		{
			return value.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(value, null));			
		}
		
		/// <summary>
		/// Generates a string containing the properties and values of the object
		/// </summary>
		/// <param name="val"></param>
		/// <returns></returns>
		public static string ToDebugString(this object val)
		{
			if (val == null) return string.Empty;
			var sb = new StringBuilder();
			var dict=val.ToDictionary();
			dict.ExecuteAction(
				kv => sb.AppendFormat("{0}={1}\n", kv.Key, kv.Value));
			return sb.ToString();
		}

		/// <summary>
		/// Creates destination object and copies source. Use CopyOptionsAttribute to mark the properties you want ignored.
		/// </summary>
		/// <seealso cref="CopyOptionsAttribute"/>
		/// <typeparam name="T">Destination type must have parameterless constructor</typeparam>
		/// <param name="source">Object to copy</param>
		public static T CopyTo<T>(this object source) where T :class, new() 
		{
			var destination = new T();
			source.CopyTo(destination);
			return destination;
		}


		/// <summary>
		/// Copy source object into destination. For ocasional use.
		/// Use Automapper for heavy duty mapping
		/// </summary>
		/// <exception cref="ArgumentNullException">If source or destinaiton are null</exception>
		/// <typeparam name="T">Destination Type</typeparam>
		/// <param name="source">Object to copy from</param>
		/// <param name="destination">Object to copy to. Unmatching or read-only properties are ignored</param>
		public static void CopyTo<T>(this object source, T destination) where T : class
		{
			if (source == null) throw new ArgumentNullException("source");
			if (destination == null) throw new ArgumentNullException("destination");
			var srcType = source.GetType();
			var attr = destination.GetType().GetSingleAttribute<CopyOptionsAttribute>();
			if (attr != null)
			{
				if (attr.IgnoreProperty) ;
			}

			foreach (var destProperty in destination.GetType().GetProperties(BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.FlattenHierarchy))
			{
				if (!destProperty.CanWrite) continue;

				var pSource = srcType.GetProperty(destProperty.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
				if (pSource == null) continue;
				var o = pSource.GetValue(source, null);
				if (!pSource.PropertyType.Equals(destProperty.PropertyType))
				{
					o = ConvertTo(o, destProperty.PropertyType);
				}
				destProperty.SetValue(destination, o, null);
			}
		}


		//static
		//    List<Tuple<Type, string, GenericGetter>> getters= new List<Tuple<Type, string, GenericGetter>>();
		
		//static List<Tuple<Type, string, GenericSetter>> setters= new List<Tuple<Type, string, GenericSetter>>();

		//public static void CopyTo<T>(this object source, T destination) where T : class
		//{
			
		//    if (source == null) throw new ArgumentNullException("source");
		//    if (destination == null) throw new ArgumentNullException("destination");
		//    var srcType = source.GetType();

		//    foreach (var destProperty in destination.GetType().GetProperties(BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.FlattenHierarchy))
		//    {
		//        if (!destProperty.CanWrite) continue;
		//        var attr = destProperty.GetSingleAttribute<CopyOptionsAttribute>();
		//        if (attr != null)
		//        {
		//            if (attr.IgnoreProperty) continue;
		//        }

		//        var pSource = srcType.GetProperty(destProperty.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
		//        if (pSource == null) continue;

		//        var tuple = getters.Find(d => d.Item1 == srcType && d.Item2 == pSource.Name);
		//        if (tuple==null)
		//        {
		//            tuple=new Tuple<Type, string, GenericGetter>(srcType,pSource.Name,CreateGetMethod(pSource));
		//            getters.Add(tuple);
		//        }

		//        var getter = tuple.Item3;
		//        var o = getter(source);

		//        if (!pSource.PropertyType.Equals(destProperty.PropertyType))
		//        {
		//            o = ConvertTo(o, destProperty.PropertyType);
		//        }

		//        var tuples = setters.Find(d => d.Item1 == srcType && d.Item2 == pSource.Name);
		//        if (tuples == null)
		//        {
		//            tuples = new Tuple<Type, string, GenericSetter>(srcType, pSource.Name, CreateSetMethod(destProperty));
		//            setters.Add(tuples);
		//        }

		//        var setter = tuples.Item3;
		//        setter(destination, o);

		//    }
		//}

		/////
		///// Creates a dynamic setter for the property
		/////
		//private static GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
		//{
		//    /*
		//    * If there's no setter return null
		//    */
		//    MethodInfo setMethod = propertyInfo.GetSetMethod();
		//    if (setMethod == null)
		//        return null;

		//    /*
		//    * Create the dynamic method
		//    */
		//    Type[] arguments = new Type[2];
		//    arguments[0] = arguments[1] = typeof(object);

		//    DynamicMethod setter = new DynamicMethod(
		//       String.Concat("_Set", propertyInfo.Name, "_"),
		//       typeof(void), arguments, propertyInfo.DeclaringType);
		//    ILGenerator generator = setter.GetILGenerator();
		//    generator.Emit(OpCodes.Ldarg_0);
		//    generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
		//    generator.Emit(OpCodes.Ldarg_1);

		//    if (propertyInfo.PropertyType.IsClass)
		//        generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
		//    else
		//        generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);

		//    generator.EmitCall(OpCodes.Callvirt, setMethod, null);
		//    generator.Emit(OpCodes.Ret);

		//    /*
		//     * Create the delegate and return it
		//     */
		//    return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
		//}

		//public delegate void GenericSetter(object target, object value);
		//public delegate object GenericGetter(object target);

		/////
		///// Creates a dynamic getter for the property
		/////
		//private static GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
		//{
		//    /*
		//    * If there's no getter return null
		//    */
		//    MethodInfo getMethod = propertyInfo.GetGetMethod();
		//    if (getMethod == null)
		//        return null;

		//    /*
		//     * Create the dynamic method
		//     */
		//    Type[] arguments = new Type[1];
		//    arguments[0] = typeof(object);

		//    DynamicMethod getter = new DynamicMethod(
		//       String.Concat("_Get", propertyInfo.Name, "_"),
		//       typeof(object), arguments, propertyInfo.DeclaringType);
		//    ILGenerator generator = getter.GetILGenerator();
		//    generator.DeclareLocal(typeof(object));
		//    generator.Emit(OpCodes.Ldarg_0);
		//    generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
		//    generator.EmitCall(OpCodes.Callvirt, getMethod, null);

		//    if (!propertyInfo.PropertyType.IsClass)
		//        generator.Emit(OpCodes.Box, propertyInfo.PropertyType);

		//    generator.Emit(OpCodes.Ret);

		//    /*
		//     * Create the delegate and return it
		//     */
		//    return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
		//}
		
		/// <summary>
		/// Converts object to type.
		/// Supports conversion to Enum, DateTime,TimeSpan
		/// </summary>
		/// <exception cref="InvalidCastException"></exception>
		/// <param name="data">Object to be converted</param>
		/// <param name="tp">Type to convert to</param>
		/// <returns></returns>
		public static object ConvertTo(this object data, Type tp)
		{
			if (data == null) throw new ArgumentNullException("data");
			if (tp.IsEnum)
			{
				if (data.GetType().FullName == "System.String")
				{
					return Enum.Parse(tp, data.ToString());
				}
				var o = Enum.ToObject(tp, data);
				return o;
			}

			if (tp.IsValueType)
			{
				if (tp == typeof(TimeSpan))
				{
					return TimeSpan.Parse(data.ToString().Trim());					
				}

				if (tp == typeof(DateTime))
				{
					data = data.ToString().Trim();
				}
			}
			if (tp == typeof(CultureInfo)) return new CultureInfo(data.ToString());
			return System.Convert.ChangeType(data, tp);
		}

		/// <summary>
		///	Tries to convert the object to type.
		/// If it fails it throws an exception
		/// </summary>
		/// <exception cref="InvalidCastException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <typeparam name="T">Type to convert to</typeparam>
		/// <param name="data">Object</param>
		/// <returns></returns>
		public static T ConvertTo<T>(this object data)
		{
			var tp = typeof(T);			
			var temp = (T)ConvertTo(data, tp);
			return temp;			
		}

		

		/// <summary>
		///	 Tries to convert the object to type.
		/// If it fails it returns the default of type.
		/// </summary>
		/// <typeparam name="T">Type to convert to</typeparam>
		/// <param name="data">Object</param>
		/// <returns></returns>
		public static T SilentConvertTo<T>(this object data)
		{
			var tp = typeof (T);  
			try
			{
				var temp = (T) ConvertTo(data, tp);
				return temp;
		}
			catch (InvalidCastException)
			{
				return default(T);
			}			
		}


		public static T As<T>(this object o)where  T:class 
		{
			if (o == null) throw new ArgumentNullException("o");
			return o as T;
		}
		
		#region Old
		///// <summary>
		///// Used for localization
		///// </summary>
		///// <param name="type">Type</param>
		///// <param name="propertyName">Public static property name</param>
		///// <returns></returns>
		//public static string GetLocalizedString(this Type type, string propertyName)
		//{
		//    var tp=type.GetProperty(propertyName);
		//    if (tp==null) return string.Empty;
		//    return (string)(tp.GetValue(null,null)??string.Empty);
		//}

		/// <summary>
		/// Copies properties from source to destination
		/// Destination contains mapping data
		/// </summary>
		//public static T MapDestinationTo<T>(this object source, BindingFlags flags) where T : new()
		//{
		//    var tp = typeof(T);
		//    var src = source.GetType();
		//    var de = new T();
		//    foreach (var destProperty in tp.GetMembers(flags))
		//    {
		//        if (destProperty.MemberType != MemberTypes.Property && destProperty.MemberType != MemberTypes.Field) continue;
		//        string name = destProperty.Name;
		//        var attr = destProperty.GetCustomAttributes(typeof(CopyAttribute), true);
		//        if (attr.Length > 0)
		//        {
		//            var at = (CopyAttribute)attr[0];
		//            if (at.IgnoreProperty) continue;
		//            name = at.Name;
		//        }

		//        if (destProperty.MemberType == MemberTypes.Property)
		//        {
		//            var pSource = src.GetProperty(name);
		//            if (pSource == null) continue;
		//            var o = pSource.GetValue(source, null);
		//            var prop = destProperty as PropertyInfo;
		//            if (!pSource.PropertyType.Equals(prop.PropertyType))
		//            {
		//                o = Convert(o, prop.PropertyType);
		//            }
		//            prop.SetValue(de, o, null);
		//        }

		//        //if (pi.MemberType == MemberTypes.Field)
		//        //{
		//        //    var p = src.GetField(name);
		//        //    if (p == null) continue;
		//        //    var o = p.GetValue(source);
		//        //    var prop = pi as FieldInfo;
		//        //    prop.SetValue(de, o);
		//        //}

		//    }
		//    return de;
		//}

	
		/// <summary>
		/// Copies public properties from source to destination.
		/// Source has mapping data
		/// </summary>
		/// <param name="source">Object</param>
		/// <param name="ignore">Properties to be ignored</param>
		//public static T MapSourceTo<T>(this object source,string[] ignore) where T : new()
		//{
		//    var tp = typeof(T);
		//    var src = source.GetType();
		//    var de = new T();
		//    foreach (var pi in src.GetProperties(BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public))
		//    {
		//        if (ignore!=null && ignore.Any(d=>d.Equals(pi.Name,StringComparison.InvariantCultureIgnoreCase))) continue;
		//        string name = pi.Name;
		//        var attr = pi.GetCustomAttributes(typeof(CopyAttribute), false);
		//        if (attr.Length > 0)
		//        {
		//            var at = (CopyAttribute)attr[0];
		//            if (at.IgnoreProperty) continue;
		//            name = at.Name;
		//        }

		//        var p = tp.GetProperty(name);
		//        if (p == null) continue;
		//        var o = pi.GetValue(source, null);
		//        p.SetValue(de, o, null);

		//        //if (pi.MemberType == MemberTypes.Field)
		//        //{
		//        //    var p = tp.GetField(name);
		//        //    if (p == null) continue;
		//        //    var prop = pi as FieldInfo;
		//        //    var o = prop.GetValue(source);
		//        //    p.SetValue(de, o);
		//        //}
		//    }
		//    return de;
		//}

		///// <summary>
		///// Copies public properties from source to destination
		///// Destination contains mapping data
		///// </summary>
		//public static T MapDestinationTo<T>(this object source) where T:new()
		//{
		//    return MapDestinationTo<T>(source, BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
		//}

		#endregion
	}
}								 